/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import Unsafe from './Unsafe';
import Exception from '../util/Exception';
import IllegalArgumentException from '../util/IllegalArgumentException';
import MalformedInputException from './MalformedInputException'
import Constants from './Constants'
import NumberTransform from './NumberTransform'
import { Strategy } from './CompressionParameters'
import Long from '../util/long/index'

export default class Util {
    constructor() {
    }

    public static highestBit(value: number): number {
        return 31 - Util.numberOfLeadingZeros(value);
    }

    public static numberOfLeadingZeros(i: number) {
        // HD, Figure 5-6
        if (i == 0)
        return 32;
        let n = 1;
        if (i >>> 16 == 0) {
            n += 16;
            i <<= 16;
        }
        if (i >>> 24 == 0) {
            n += 8;
            i <<= 8;
        }
        if (i >>> 28 == 0) {
            n += 4;
            i <<= 4;
        }
        if (i >>> 30 == 0) {
            n += 2;
            i <<= 2;
        }
        n -= i >>> 31;
        return n;
    }

    public static isPowerOf2(value: number): boolean
    {
        return (value & (value - 1)) == 0;
    }

    public static mask(bits: number): number
    {
        return (1 << bits) - 1;
    }

    public static verify(condition: boolean, offset: Long, reason: string): void
    {
        if (!condition) {
            throw new MalformedInputException(offset, reason);
        }
    }

    public static checkArgument(condition: boolean, reason: string): void {
        if (!condition) {
            throw new Exception(reason);
        }
    }

    public static checkState(condition: boolean, reason: string): void
    {
        if (!condition) {
            throw new Exception(reason);
        }
    }

    public static fail(offset: Long, reason: string): MalformedInputException
    {
        throw new MalformedInputException(offset, reason);
    }

    public static cycleLog(hashLog: number, strategy: Strategy): number {
        let cycleLog: number = hashLog;
        if (strategy == Strategy.BTLAZY2 || strategy == Strategy.BTOPT || strategy == Strategy.BTULTRA) {
            cycleLog = hashLog - 1;
        }
        return cycleLog;
    }

    public static put24BitLittleEndian(outputBase: any, outputAddress: Long, value: number): void
    {
        Unsafe.putShort(outputBase, outputAddress.toNumber(), NumberTransform.toShort(value));
        Unsafe.putByte(outputBase,
        outputAddress.add(Constants.SIZE_OF_SHORT).toNumber(), NumberTransform.toByte(value >>> 16));
    }

    public static minTableLog(inputSize: number, maxSymbolValue: number): number
    {
        if (inputSize <= 1) {
            throw new IllegalArgumentException("Not supported. RLE should be used instead"); // TODO
        }

        let minBitsSrc: number = this.highestBit((inputSize - 1)) + 1;
        let minBitsSymbols: number = this.highestBit(maxSymbolValue) + 2;
        return Math.min(minBitsSrc, minBitsSymbols);
    }
}
